package org.hxzon.configdesigner.web.components;

import java.util.Arrays;
import java.util.List;

import org.apache.tapestry5.ComponentAction;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.ValueEncoder;
import org.apache.tapestry5.annotations.BeforeRenderBody;
import org.apache.tapestry5.annotations.BeginRender;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.func.F;
import org.apache.tapestry5.func.Flow;
import org.apache.tapestry5.func.Worker;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.runtime.RenderCommand;
import org.apache.tapestry5.runtime.RenderQueue;
import org.apache.tapestry5.services.FormSupport;
import org.hxzon.configdesigner.core.CfgInfo;
import org.hxzon.configdesigner.core.CfgValue;

public class CfgValueTree {
    @Parameter(required = true, autoconnect = true)
    private CfgValue cfg;//root

    //
    @Property
    @Parameter
    private CfgValue value;//curValue

//    @Parameter
//    private RenderCommand body;

    @Parameter
    private ValueEncoder<CfgValue> encoder;

    //
    @Environmental(false)
    private FormSupport formSupport;

    @Inject
    private ComponentResources resources;
    //========================
    private static RenderCommand RENDER_CLOSE_TAG = new RenderCommand() {
        public void render(MarkupWriter writer, RenderQueue queue) {
            writer.end();
        }
    };

    private RenderCommand toRenderCommand(final CfgValue node) {
        return new RenderCommand() {
            public void render(MarkupWriter writer, RenderQueue queue) {
                value = node;

                writer.element("li");
                queue.push(RENDER_CLOSE_TAG);

                if (isEmbedCombo(node)) {
                    queue.push(new RenderNodes(node.getValues()));
                }

                queue.push((RenderCommand) resources.getBody());
                //queue.push(body);
                //动作顺序：重建值动作，Field动作。
                String clientValue = encoder.toClient(value);
                formSupport.store(CfgValueTree.this, new RestoreStateFromStoredClientValue(clientValue));
            }
        };
    }

    private boolean isEmbedCombo(CfgValue cfgValue) {
        if (cfgValue == cfg || //
                (cfgValue.getCfgInfo().getType() > CfgInfo.Type_Combo //
                && cfgValue.getCfgInfo().isEmbed())) {
            return true;
        }
        return false;
    }

    private class RenderNodes implements RenderCommand {
        private final Flow<CfgValue> nodes;

        public RenderNodes(List<CfgValue> nodes) {
            this.nodes = F.flow(nodes).reverse();
        }

        public void render(MarkupWriter writer, final RenderQueue queue) {
            if (nodes.isEmpty()) {
                return;
            }
            writer.element("ul");
            queue.push(RENDER_CLOSE_TAG);

            nodes.each(new Worker<CfgValue>() {
                public void work(CfgValue element) {
                    queue.push(toRenderCommand(element));
                }
            });
        }

    }

    @BeginRender
    Object begin() {
        return new RenderNodes(Arrays.asList(new CfgValue[] { cfg }));
    }

    @BeforeRenderBody
    Object beforeRenderBody() {
        return false;
    }

    //
    @SuppressWarnings("serial")
    static class RestoreStateFromStoredClientValue implements ComponentAction<CfgValueTree> {
        private final String clientValue;

        public RestoreStateFromStoredClientValue(final String clientValue) {
            this.clientValue = clientValue;
        }

        public void execute(CfgValueTree component) {
            component.restoreStateFromStoredClientValue(clientValue);
        }

        @Override
        public String toString() {
            return String.format("CfgRender.RestoreStateFromStoredClientValue[%s]", clientValue);
        }
    }

    private void restoreStateFromStoredClientValue(String clientValue) {
        value = encoder.toValue(clientValue);
    }

}
